Poznaj zaawansowane dopasowywanie wzorców JavaScript za pomocą wyrażeń regularnych. Dowiedz się o składni regex, praktycznych zastosowaniach i technikach optymalizacji dla wydajnego i niezawodnego kodu.
Dopasowywanie wzorców JavaScript za pomocą wyrażeń regularnych: Kompleksowy przewodnik
Wyrażenia regularne (regex) to potężne narzędzie do dopasowywania wzorców i manipulacji tekstem w JavaScript. Pozwalają programistom na wyszukiwanie, walidację i transformację ciągów znaków na podstawie zdefiniowanych wzorców. Ten przewodnik zawiera kompleksowy przegląd wyrażeń regularnych w JavaScript, obejmujący składnię, użycie i zaawansowane techniki.
Czym są wyrażenia regularne?
Wyrażenie regularne to sekwencja znaków, która definiuje wzorzec wyszukiwania. Wzorce te służą do dopasowywania i manipulowania ciągami znaków. Wyrażenia regularne są szeroko stosowane w programowaniu do zadań takich jak:
- Walidacja danych: Upewnianie się, że dane wejściowe użytkownika są zgodne z określonymi formatami (np. adresy e-mail, numery telefonów).
- Ekstrakcja danych: Pobieranie określonych informacji z tekstu (np. ekstrakcja dat, adresów URL lub cen).
- Wyszukiwanie i zamiana: Wyszukiwanie i zamiana tekstu na podstawie złożonych wzorców.
- Przetwarzanie tekstu: Dzielenie, łączenie lub transformacja ciągów znaków na podstawie zdefiniowanych reguł.
Tworzenie wyrażeń regularnych w JavaScript
W JavaScript wyrażenia regularne można tworzyć na dwa sposoby:
- Używając literału wyrażenia regularnego: Umieść wzorzec w ukośnikach (
/). - Używając konstruktora
RegExp: Utwórz obiektRegExpz wzorcem jako ciągiem znaków.
Przykład:
// Używanie literału wyrażenia regularnego
const regexLiteral = /hello/;
// Używanie konstruktora RegExp
const regexConstructor = new RegExp("hello");
Wybór między tymi dwiema metodami zależy od tego, czy wzorzec jest znany w czasie kompilacji, czy jest generowany dynamicznie. Użyj notacji literału, gdy wzorzec jest stały i znany z góry. Użyj konstruktora, gdy wzorzec musi być zbudowany programowo, szczególnie podczas włączania zmiennych.
Podstawowa składnia wyrażeń regularnych
Wyrażenia regularne składają się ze znaków, które reprezentują wzorzec do dopasowania. Oto kilka podstawowych komponentów wyrażeń regularnych:
- Znaki literału: Dopasowują same znaki (np.
/a/dopasowuje znak „a”). - Metaznaki: Mają specjalne znaczenia (np.
.,^,$,*,+,?,[],{},(),\,|). - Klasy znaków: Reprezentują zestawy znaków (np.
[abc]dopasowuje „a”, „b” lub „c”). - Kwantyfikatory: Określają, ile razy dany znak lub grupa powinna wystąpić (np.
*,+,?,{n},{n,},{n,m}). - Kotwice: Dopasowują pozycje w ciągu znaków (np.
^dopasowuje początek,$dopasowuje koniec).
Typowe metaznaki:
.(kropka): Dopasowuje dowolny pojedynczy znak z wyjątkiem znaku nowej linii.^(daszek): Dopasowuje początek ciągu znaków.$(dolar): Dopasowuje koniec ciągu znaków.*(gwiazdka): Dopasowuje zero lub więcej wystąpień poprzedzającego znaku lub grupy.+(plus): Dopasowuje jedno lub więcej wystąpień poprzedzającego znaku lub grupy.?(znak zapytania): Dopasowuje zero lub jedno wystąpienie poprzedzającego znaku lub grupy. Używany do znaków opcjonalnych.[](nawiasy kwadratowe): Definiuje klasę znaków, dopasowując dowolny pojedynczy znak w nawiasach.{}(nawiasy klamrowe): Określa liczbę wystąpień do dopasowania.{n}dopasowuje dokładnie n razy,{n,}dopasowuje n lub więcej razy,{n,m}dopasowuje od n do m razy.()(nawiasy okrągłe): Grupuje znaki razem i przechwytuje dopasowany podciąg.\(ukośnik wsteczny): Ucieka metaznakom, pozwalając na ich dosłowne dopasowanie.|(pionowa kreska): Działa jako operator „lub”, dopasowując wyrażenie przed lub po nim.
Klasy znaków:
[abc]: Dopasowuje dowolny z znaków a, b lub c.[^abc]: Dopasowuje dowolny znak, który *nie* jest a, b lub c.[a-z]: Dopasowuje dowolną małą literę od a do z.[A-Z]: Dopasowuje dowolną wielką literę od A do Z.[0-9]: Dopasowuje dowolną cyfrę od 0 do 9.[a-zA-Z0-9]: Dopasowuje dowolny znak alfanumeryczny.\d: Dopasowuje dowolną cyfrę (odpowiednik[0-9]).\D: Dopasowuje dowolny znak niebędący cyfrą (odpowiednik[^0-9]).\w: Dopasowuje dowolny znak słowny (alfanumeryczny plus podkreślenie; odpowiednik[a-zA-Z0-9_]).\W: Dopasowuje dowolny znak nienależący do słów (odpowiednik[^a-zA-Z0-9_]).\s: Dopasowuje dowolny znak odstępu (spacja, tabulator, nowa linia itp.).\S: Dopasowuje dowolny znak niebędący odstępem.
Kwantyfikatory:
*: Dopasowuje poprzedni element zero lub więcej razy. Na przykład,a*dopasowuje „”, „a”, „aa”, „aaa” i tak dalej.+: Dopasowuje poprzedni element jeden lub więcej razy. Na przykład,a+dopasowuje „a”, „aa”, „aaa”, ale nie „”.?: Dopasowuje poprzedni element zero lub jeden raz. Na przykład,a?dopasowuje „” lub „a”.{n}: Dopasowuje poprzedni element dokładnie *n* razy. Na przykład,a{3}dopasowuje „aaa”.{n,}: Dopasowuje poprzedni element *n* lub więcej razy. Na przykład,a{2,}dopasowuje „aa”, „aaa”, „aaaa” i tak dalej.{n,m}: Dopasowuje poprzedni element od *n* do *m* razy (włącznie). Na przykład,a{2,4}dopasowuje „aa”, „aaa” lub „aaaa”.
Kotwice:
^: Dopasowuje początek ciągu znaków. Na przykład,^Hellodopasowuje ciągi, które *zaczynają się* od „Hello”.$: Dopasowuje koniec ciągu znaków. Na przykład,World$dopasowuje ciągi, które *kończą się* na „World”.\b: Dopasowuje granicę słowa. Jest to pozycja między znakiem słownym (\w) a znakiem niesłownym (\W) lub początkiem lub końcem ciągu znaków. Na przykład,\bword\bdopasowuje całe słowo „word”.
Flagi:
Flagi wyrażeń regularnych modyfikują działanie wyrażeń regularnych. Są dołączane na końcu literału wyrażenia regularnego lub przekazywane jako drugi argument do konstruktora RegExp.
g(global): Dopasowuje wszystkie wystąpienia wzorca, a nie tylko pierwsze.i(ignoruj wielkość liter): Wykonuje dopasowywanie bez uwzględniania wielkości liter.m(wielowierszowy): Włącza tryb wielowierszowy, w którym^i$dopasowują początek i koniec każdego wiersza (oddzielonego przez\n).s(dotAll): Umożliwia kropce (.) dopasowywanie również znaków nowej linii.u(unicode): Włącza pełną obsługę Unicode.y(sticky): Dopasowuje tylko od indeksu wskazanego przez właściwośćlastIndexwyrażenia regularnego.
Metody wyrażeń regularnych JavaScript
JavaScript udostępnia kilka metod do pracy z wyrażeniami regularnymi:
test(): Sprawdza, czy ciąg znaków pasuje do wzorca. Zwracatruelubfalse.exec(): Wykonuje wyszukiwanie dopasowania w ciągu znaków. Zwraca tablicę zawierającą dopasowany tekst i przechwycone grupy lubnull, jeśli nie znaleziono dopasowania.match(): Zwraca tablicę zawierającą wyniki dopasowywania ciągu znaków do wyrażenia regularnego. Zachowuje się inaczej z i bez flagig.search(): Sprawdza dopasowanie w ciągu znaków. Zwraca indeks pierwszego dopasowania lub -1, jeśli nie znaleziono dopasowania.replace(): Zastępuje wystąpienia wzorca ciągiem zastępczym lub funkcją, która zwraca ciąg zastępczy.split(): Dzieli ciąg znaków na tablicę podciągów na podstawie wyrażenia regularnego.
Przykłady użycia metod wyrażeń regularnych:
// test()
const regex = /hello/;
const str = "hello world";
console.log(regex.test(str)); // Wyjście: true
// exec()
const regex2 = /hello (\w+)/;
const str2 = "hello world";
const result = regex2.exec(str2);
console.log(result); // Wyjście: ["hello world", "world", index: 0, input: "hello world", groups: undefined]
// match() z flagą 'g'
const regex3 = /\d+/g; // Dopasowuje jedną lub więcej cyfr globalnie
const str3 = "There are 123 apples and 456 oranges.";
const matches = str3.match(regex3);
console.log(matches); // Wyjście: ["123", "456"]
// match() bez flagi 'g'
const regex4 = /\d+/;
const str4 = "There are 123 apples and 456 oranges.";
const match = str4.match(regex4);
console.log(match); // Wyjście: ["123", index: 11, input: "There are 123 apples and 456 oranges.", groups: undefined]
// search()
const regex5 = /world/;
const str5 = "hello world";
console.log(str5.search(regex5)); // Wyjście: 6
// replace()
const regex6 = /world/;
const str6 = "hello world";
const newStr = str6.replace(regex6, "JavaScript");
console.log(newStr); // Wyjście: hello JavaScript
// replace() z funkcją
const regex7 = /(\d+)-(\d+)-(\d+)/;
const str7 = "Today's date is 2023-10-27";
const newStr2 = str7.replace(regex7, (match, year, month, day) => {
return `${day}/${month}/${year}`;
});
console.log(newStr2); // Wyjście: Today's date is 27/10/2023
// split()
const regex8 = /, /;
const str8 = "apple, banana, cherry";
const arr = str8.split(regex8);
console.log(arr); // Wyjście: ["apple", "banana", "cherry"]
Zaawansowane techniki wyrażeń regularnych
Grupy przechwytujące:
Nawiasy () służą do tworzenia grup przechwytujących w wyrażeniach regularnych. Przechwycone grupy pozwalają na wyodrębnienie określonych części dopasowanego tekstu. Metody exec() i match() zwracają tablicę, w której pierwszy element to całe dopasowanie, a kolejne elementy to przechwycone grupy.
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const dateString = "2023-10-27";
const match = regex.exec(dateString);
console.log(match[0]); // Wyjście: 2023-10-27 (Całe dopasowanie)
console.log(match[1]); // Wyjście: 2023 (Pierwsza przechwycona grupa - rok)
console.log(match[2]); // Wyjście: 10 (Druga przechwycona grupa - miesiąc)
console.log(match[3]); // Wyjście: 27 (Trzecia przechwycona grupa - dzień)
Nazwane grupy przechwytujące:
ES2018 wprowadził nazwane grupy przechwytujące, które pozwalają na przypisywanie nazw do grup przechwytujących za pomocą składni (?<name>...). Ułatwia to czytelność i konserwację kodu.
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const dateString = "2023-10-27";
const match = regex.exec(dateString);
console.log(match.groups.year); // Wyjście: 2023
console.log(match.groups.month); // Wyjście: 10
console.log(match.groups.day); // Wyjście: 27
Grupy nieprzechwytujące:
Jeśli chcesz zgrupować części wyrażenia regularnego bez ich przechwytywania (np. w celu zastosowania kwantyfikatora do grupy), możesz użyć grupy nieprzechwytującej ze składnią (?:...). Pozwala to uniknąć niepotrzebnego przydzielania pamięci dla przechwyconych grup.
const regex = /(?:https?:\/\/)?([\w\.]+)/; // Dopasowuje adres URL, ale przechwytuje tylko nazwę domeny
const url = "https://www.example.com/path";
const match = regex.exec(url);
console.log(match[1]); // Wyjście: www.example.com
Lookarounds:
Lookarounds to asercje o zerowej szerokości, które dopasowują pozycję w ciągu znaków na podstawie wzorca, który poprzedza (lookbehind) lub następuje (lookahead) po tej pozycji, bez uwzględniania wzorca lookaround w samym dopasowaniu.
- Positive Lookahead:
(?=...)Dopasowuje, jeśli wzorzec wewnątrz lookahead *następuje* po bieżącej pozycji. - Negative Lookahead:
(?!...)Dopasowuje, jeśli wzorzec wewnątrz lookahead *nie* następuje po bieżącej pozycji. - Positive Lookbehind:
(?<=...)Dopasowuje, jeśli wzorzec wewnątrz lookbehind *poprzedza* bieżącą pozycję. - Negative Lookbehind:
(?<!...)Dopasowuje, jeśli wzorzec wewnątrz lookbehind *nie* poprzedza bieżącej pozycji.
Przykład:
// Positive Lookahead: Pobierz cenę tylko wtedy, gdy następuje po USD
const regex = /\d+(?= USD)/;
const text = "The price is 100 USD";
const match = text.match(regex);
console.log(match); // Wyjście: ["100"]
// Negative Lookahead: Pobierz słowo tylko wtedy, gdy nie następuje po nim liczba
const regex2 = /\b\w+\b(?! \d)/;
const text2 = "apple 123 banana orange 456";
const matches = text2.match(regex2);
console.log(matches); // Wyjście: null because match() only returns the first match without 'g' flag, which isn't what we need.
// to fix it:
const regex3 = /\b\w+\b(?! \d)/g;
const text3 = "apple 123 banana orange 456";
const matches3 = text3.match(regex3);
console.log(matches3); // Wyjście: [ 'banana' ]
// Positive Lookbehind: Pobierz wartość tylko wtedy, gdy poprzedzona znakiem $
const regex4 = /(?<=\$)\d+/;
const text4 = "The price is $200";
const match4 = text4.match(regex4);
console.log(match4); // Wyjście: ["200"]
// Negative Lookbehind: Get the word only when not preceded by the word 'not'
const regex5 = /(?<!not )\w+/;
const text5 = "I am not happy, I am content.";
const match5 = text5.match(regex5); //returns first match if matched, not the array
console.log(match5); // Output: ['am', index: 2, input: 'I am not happy, I am content.', groups: undefined]
// to fix it, use g flag and exec(), but be careful since regex.exec saves the index
const regex6 = /(?<!not )\w+/g;
let text6 = "I am not happy, I am content.";
let match6; let matches6=[];
while ((match6 = regex6.exec(text6)) !== null) {
matches6.push(match6[0]);
}
console.log(matches6); // Wyjście: [ 'I', 'am', 'happy', 'I', 'am', 'content' ]
Backreferences:
Odwołania wsteczne pozwalają na odwoływanie się do wcześniej przechwyconych grup w tym samym wyrażeniu regularnym. Używają składni \1, \2 itp., gdzie numer odpowiada numerowi przechwyconej grupy.
const regex = /([a-z]+) \1/;
const text = "hello hello world";
const match = regex.exec(text);
console.log(match); // Wyjście: ["hello hello", "hello", index: 0, input: "hello hello world", groups: undefined]
Praktyczne zastosowania wyrażeń regularnych
Walidacja adresów e-mail:
Typowym przypadkiem użycia wyrażeń regularnych jest walidacja adresów e-mail. Chociaż idealne wyrażenie regularne do walidacji e-mail jest niezwykle złożone, oto uproszczony przykład:
const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
console.log(emailRegex.test("test@example.com")); // Wyjście: true
console.log(emailRegex.test("invalid-email")); // Wyjście: false
console.log(emailRegex.test("test@sub.example.co.uk")); // Wyjście: true
Ekstrakcja adresów URL z tekstu:
Możesz użyć wyrażeń regularnych do wyodrębnienia adresów URL z bloku tekstu:
const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)/g;
const text = "Odwiedź naszą stronę internetową pod adresem https://www.example.com lub sprawdź http://blog.example.org.";
const urls = text.match(urlRegex);
console.log(urls); // Wyjście: ["https://www.example.com", "http://blog.example.org"]
Parsowanie danych CSV:
Wyrażenia regularne mogą służyć do parsowania danych CSV (wartości rozdzielane przecinkami). Oto przykład dzielenia ciągu CSV na tablicę wartości, obsługi pól w cudzysłowach:
const csvString = 'John,Doe,"123, Main St",New York';
const csvRegex = /(?:"([^"]*(?:""[^"*)*)")|([^,]+)/g; //Corrected CSV regex
let values = [];
let match;
while (match = csvRegex.exec(csvString)) {
values.push(match[1] ? match[1].replace(/""/g, '"') : match[2]);
}
console.log(values); // Wyjście: ["John", "Doe", "123, Main St", "New York"]
Walidacja międzynarodowego numeru telefonu
Walidacja międzynarodowych numerów telefonów jest skomplikowana ze względu na różne formaty i długości. Solidne rozwiązanie często wymaga użycia biblioteki, ale uproszczone wyrażenie regularne może zapewnić podstawową walidację:
const phoneRegex = /^\+(?:[0-9] ?){6,14}[0-9]$/;
console.log(phoneRegex.test("+1 555 123 4567")); // Wyjście: true (Przykład z USA)
console.log(phoneRegex.test("+44 20 7946 0500")); // Wyjście: true (Przykład z Wielkiej Brytanii)
console.log(phoneRegex.test("+81 3 3224 5000")); // Wyjście: true (Przykład z Japonii)
console.log(phoneRegex.test("123-456-7890")); // Wyjście: false
Walidacja siły hasła
Wyrażenia regularne są przydatne do egzekwowania zasad dotyczących siły haseł. Poniższy przykład sprawdza minimalną długość, wielkie litery, małe litery i liczbę.
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/;
console.log(passwordRegex.test("P@ssword123")); // Wyjście: true
console.log(passwordRegex.test("password")); // Wyjście: false (brak wielkich liter lub liczby)
console.log(passwordRegex.test("Password")); // Wyjście: false (brak liczby)
console.log(passwordRegex.test("Pass123")); // Wyjście: false (brak małych liter)
console.log(passwordRegex.test("P@ss1")); // Wyjście: false (mniej niż 8 znaków)
Techniki optymalizacji wyrażeń regularnych
Wyrażenia regularne mogą być kosztowne obliczeniowo, szczególnie w przypadku złożonych wzorców lub dużych danych wejściowych. Oto kilka technik optymalizacji wydajności wyrażeń regularnych:
- Bądź precyzyjny: Unikaj używania zbyt ogólnych wzorców, które mogą dopasowywać więcej niż zamierzasz.
- Używaj kotwic: Zakotwicz wyrażenie regularne na początku lub na końcu ciągu znaków, jeśli to możliwe (
^,$). - Unikaj wycofywania: Minimalizuj wycofywanie, używając kwantyfikatorów dzierżawczych (np.
++zamiast+) lub grup atomowych ((?>...)), jeśli to właściwe. - Kompiluj raz: Jeśli używasz tego samego wyrażenia regularnego wiele razy, skompiluj je raz i ponownie użyj obiektu
RegExp. - Używaj klas znaków mądrze: Klasy znaków (
[]) są generalnie szybsze niż alternacje (|). - Utrzymuj prostotę: Unikaj zbyt złożonych wyrażeń regularnych, które są trudne do zrozumienia i utrzymania. Czasami podzielenie złożonego zadania na wiele prostszych wyrażeń regularnych lub użycie innych technik manipulacji ciągami może być bardziej wydajne.
Typowe błędy w wyrażeniach regularnych
- Zapominanie o ucieczce metaznaków: Niezamieszczanie znaków specjalnych takich jak
.,*,+,?,$,^,(,),[,],{,},|i\, gdy chcesz dopasować je dosłownie. - Nadmierne używanie
.(kropki): Kropka pasuje do dowolnego znaku (z wyjątkiem nowej linii w niektórych trybach), co może prowadzić do nieoczekiwanych dopasowań, jeśli nie jest używana ostrożnie. Bądź bardziej precyzyjny, jeśli to możliwe, używając klas znaków lub innych bardziej restrykcyjnych wzorców. - Chciwość: Domyślnie kwantyfikatory takie jak
*i+są chciwe i dopasowują jak najwięcej. Użyj kwantyfikatorów leniwych (*?,+?), gdy musisz dopasować najkrótszy możliwy ciąg znaków. - Nieprawidłowe używanie kotwic: Niezrozumienie zachowania
^(początek ciągu znaków/linii) i$(koniec ciągu znaków/linii) może prowadzić do nieprawidłowego dopasowania. Pamiętaj, aby użyć flagim(wielowierszowa) podczas pracy z ciągami wielowierszowymi i chcesz, aby^i$pasowały do początku i końca każdego wiersza. - Niezadbanie o przypadki brzegowe: Niezrozumienie wszystkich możliwych scenariuszy wejściowych i przypadków brzegowych może prowadzić do błędów. Przetestuj dokładnie swoje wyrażenia regularne za pomocą różnych danych wejściowych, w tym pustych ciągów znaków, nieprawidłowych znaków i warunków brzegowych.
- Problemy z wydajnością: Tworzenie zbyt złożonych i nieefektywnych wyrażeń regularnych może powodować problemy z wydajnością, szczególnie w przypadku dużych danych wejściowych. Zoptymalizuj swoje wyrażenia regularne, używając bardziej szczegółowych wzorców, unikając niepotrzebnego wycofywania i kompilując wyrażenia regularne, które są używane wielokrotnie.
- Ignorowanie kodowania znaków: Niewłaściwe obsługiwanie kodowania znaków (szczególnie Unicode) może prowadzić do nieoczekiwanych wyników. Użyj flagi
upodczas pracy ze znakami Unicode, aby zapewnić prawidłowe dopasowanie.
Podsumowanie
Wyrażenia regularne są cennym narzędziem do dopasowywania wzorców i manipulacji tekstem w JavaScript. Opanowanie składni i technik wyrażeń regularnych pozwala na efektywne rozwiązywanie szerokiego zakresu problemów, od walidacji danych po złożone przetwarzanie tekstu. Rozumiejąc pojęcia omówione w tym przewodniku i ćwicząc na przykładach z rzeczywistego świata, możesz stać się biegłym w używaniu wyrażeń regularnych, aby zwiększyć swoje umiejętności tworzenia stron internetowych w JavaScript.
Pamiętaj, że wyrażenia regularne mogą być złożone i często pomocne jest dokładne przetestowanie ich za pomocą internetowych testerów regex, takich jak regex101.com lub regexr.com. Pozwala to na wizualizację dopasowań i skuteczne debugowanie wszelkich problemów. Miłego kodowania!